﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SysImaging = System.Windows.Media.Imaging;
using iNet.Media.Imaging.Exif;
using iNet.Media.Imaging.Jpeg;
using iNet.Media.Xmp;
using iNet.Tasks;

namespace iNet.Media.Imaging
{
	/// <summary>
	/// JPEG 影像編碼工作。
	/// </summary>
	public class JpegImageEncodeTask : BasicImageEncodeTask
	{
		#region 欄位
		int _QualityLevel = 90;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 JpegImageEncodeTask 執行個體。
		/// </summary>
		public JpegImageEncodeTask()
			: base(ImageFormats.Jpeg)
		{ }
		/// <summary>
		/// 初始化新的 JpegImageEncodeTask 執行個體。
		/// </summary>
		/// <param name="destination">儲存編碼結果的媒體儲存。</param>
		public JpegImageEncodeTask(MediaStorage destination)
			: this()
		{
			this.Destination = destination;
		}
		#endregion
		#region CreateCompatibleMetadata
		/// <summary>
		/// 建立與此媒體格式相容且可編輯的中繼資料。
		/// </summary>
		/// <param name="template">現有的中繼資料。</param>
		/// <returns>與此媒體格式相容的中繼資料。</returns>
		public override IMetadata CreateCompatibleMetadata(IMetadata template)
		{
			TiffMetadata metadata = (template as TiffMetadata);
			if (metadata == null || metadata.IsReadOnly)
				return new TiffMetadata(template, false);
			return metadata;
		}
		#endregion
		#region CreateEncoder
		internal override SysImaging.BitmapEncoder CreateEncoder()
		{
			return new SysImaging.JpegBitmapEncoder();
		}
		#endregion
		#region Encode
		protected override bool Encode(TaskVersion version, MediaStorage destination, MediaEncodeRequests mediaEncodeReqs, ImageEncodeRequests imageEncodeReqs)
		{
			//編碼影像
			if (!base.Encode(version, destination, mediaEncodeReqs, imageEncodeReqs))
				return false;

			//中斷確認
			if (this.IsCanceling)
				return true;

			//編碼中繼資料
			if ((mediaEncodeReqs & Media.MediaEncodeRequests.EncodeMetadata) != 0)
			{
				//取得中繼資料
				IMetadata metadata = this.EncodingMetadata;
				if (metadata == null && this.EncodingImage != null)
					metadata = this.EncodingImage.Metadata;

				//編碼中繼資料
				if (metadata != null)
				{
					Stream stream;
					if (!this.GetMediaStorageStream(version, destination, 0, out stream))
						throw new MediaEncodeFailedException(MediaEncodeFailedReason.CannotAccessStream);
					try
					{
						if (!this.EncodeMetadata(stream, metadata) || !this.CommitToMediaStorage(version, destination, 0, stream))
							return false;
					}
					finally
					{
						this.ReleaseMediaStorageStream(version, destination, stream);
					}
				}
			}

			//完成
			return true;
		}
		#endregion
		#region EncodeMetadata
		bool EncodeMetadata(Stream stream, IMetadata metadata)
		{
			//取得起始位置
			long headerPosition = stream.Position;

			//確認格式
			byte[] buffer4 = new byte[4];
			if (stream.Read(buffer4, 0, 2) < 2 || buffer4[0] != 0xFF || buffer4[1] != 0xD8)
				throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);

			//取出 Segment
			byte[] buffer;
			byte[] app0Segment = null;
			byte[] sosSegment = null;
			List<byte[]> otherSegments = new List<byte[]>();
			BitConverter converter = BitConverter.BigEndian;
			while (stream.Position < stream.Length)
			{
				//取得 Segment 類型及長度
				if (stream.Read(buffer4, 0, 4) < 4 || buffer4[0] != 0xFF)
					throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);
				JpegSegmentType type = (JpegSegmentType)buffer4[1];
				int length = converter.ToUInt16(buffer4, 2) - 2;
				if (length < 0)
					throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);

				//讀取 Segment
				buffer = new byte[4 + length];
				if (stream.Read(buffer, 4, length) < length)
					throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);
				buffer[0] = 0xFF;
				buffer[1] = (byte)type;
				converter.GetBytes((ushort)(length + 2)).CopyTo(buffer, 2);

				//暫存 Segment
				if (type == JpegSegmentType.SOS)
				{
					//讀取影像資料
					int imageDataLength = (int)(stream.Length - 2 - stream.Position);
					byte[] sosBuffer = new byte[4 + length + imageDataLength];
					if (stream.Read(sosBuffer, 4 + length, imageDataLength) < imageDataLength)
						throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);

					//暫存 Segment
					buffer.CopyTo(sosBuffer, 0);
					sosSegment = sosBuffer;

					//讀取完成
					break;
				}
				else
				{
					switch (type)
					{
						case JpegSegmentType.APP0:
							app0Segment = buffer;
							break;
						case JpegSegmentType.APP1:
							break;
						default:
							otherSegments.Add(buffer);
							break;
					}
				}
			}

			//確認資料正確性
			if (sosSegment == null)
				throw new MediaEncodeFailedException(MediaEncodeFailedReason.InvalidFormat);

			//轉換成 TIFF 中繼資料
			if (!(metadata is TiffMetadata))
				metadata = new TiffMetadata(metadata, true);

			//取得 EXIF Tag 及 XMP Property
			List<ExifTag> exifTags = new List<ExifTag>((metadata as IExifMetadata).ExifTags);
			List<XmpProperty> xmpProperties = new List<XmpProperty>((metadata as IXmpMetadata).XmpProperties);
			
			//建立 Exif Packet
			byte[] exifPacket;
			if (exifTags.Count > 0)
			{
				using (MemoryStream mStream = new MemoryStream())
				{
					TiffWriter tiffWriter = new TiffWriter(mStream, null, BitConverter.LittleEndian);
					foreach (ExifTag tag in exifTags)
						tiffWriter.WriteTag(tag);
					tiffWriter.Commit();
					exifPacket = mStream.ToArray();
				}
			}
			else
				exifPacket = null;

			//建立 XMP Packet
			byte[] xmpPacket;
			if (xmpProperties.Count > 0)
				xmpPacket = (new XmpWriter()).Write(xmpProperties);
			else
				xmpPacket = null;

			//寫入 SOI Segment
			stream.Position = headerPosition;
			buffer = new byte[] { 0xFF, 0xD8 };
			stream.Write(buffer, 0, 2);

			//寫入 APP0 Segment
			if (app0Segment != null)
				stream.Write(app0Segment, 0, app0Segment.Length);

			//寫入 Exif Packet
			if (exifPacket != null && exifPacket.Length > 0)
			{
				buffer[1] = (byte)JpegSegmentType.APP1;
				buffer4 = new byte[] { 0x45, 0x78, 0x69, 0x66 };
				stream.Write(buffer, 0, 2);
				stream.Write(converter.GetBytes((ushort)(exifPacket.Length + 8)), 0, 2);
				stream.Write(buffer4, 0, 4);
				stream.Write(new byte[2], 0, 2);
				stream.Write(exifPacket, 0, exifPacket.Length);
			}

			//寫入 XMP Packet
			if (xmpPacket != null && xmpPacket.Length > 0)
			{
				buffer[1] = (byte)JpegSegmentType.APP1;
				stream.Write(buffer, 0, 2);
				stream.Write(converter.GetBytes((ushort)(xmpPacket.Length + 31)), 0, 2);
				stream.Write(Encoding.ASCII.GetBytes("http://ns.adobe.com/xap/1.0/\0"), 0, 29);
				stream.Write(xmpPacket, 0, xmpPacket.Length);
			}

			//寫入其他 Segment
			foreach (byte[] segment in otherSegments)
				stream.Write(segment, 0, segment.Length);

			//寫入 SOS Segment 及影像資料
			stream.Write(sosSegment, 0, sosSegment.Length);

			//寫入 EOI Segment
			buffer[1] = 0xD9;
			stream.Write(buffer, 0, 2);

			//設定資料流長度
			stream.SetLength(stream.Position);

			//編碼完成
			return true;
		}
		#endregion
		#region InitializeEncoderOverride
		internal override void InitializeEncoderOverride(SysImaging.BitmapEncoder encoder, IImage image)
		{
			//呼叫基底
			base.InitializeEncoderOverride(encoder, image);

			//初始化 JpegBitmapEncoder
			SysImaging.JpegBitmapEncoder jpegEncoder = (encoder as SysImaging.JpegBitmapEncoder);
			jpegEncoder.QualityLevel = _QualityLevel;
		}
		#endregion
		#region QualityLevel
		/// <summary>
		/// 取得或設定影像品質，1 為最低品質；100 為最高品質，預設值為 90。
		/// </summary>
		public int QualityLevel
		{
			get
			{
				return _QualityLevel;
			}
			set
			{
				this.SetInitializingParameter<int>(ref _QualityLevel, value);
			}
		}
		#endregion
	}

	/// <summary>
	/// JPEG 影像編碼工作工廠。
	/// </summary>
	public class JpegImageEncodeTaskFactory : ImageEncodeTaskFactory
	{
		#region 建構子
		/// <summary>
		/// 初始化新的 JpegImageEncodeTaskFactory 執行個體。
		/// </summary>
		public JpegImageEncodeTaskFactory()
			: base("JPEG Image EncodeTask Factory", ImageFormats.Jpeg)
		{ }
		#endregion
		#region CreateEncodeTask
		/// <summary>
		/// 建立影像編碼工作。
		/// </summary>
		/// <param name="destination">儲存編碼結果的媒體儲存。</param>
		/// <returns>影像編碼工作。</returns>
		public override MediaEncodeTask CreateEncodeTask(MediaStorage destination)
		{
			return new JpegImageEncodeTask(destination);
		}
		#endregion
	}
}
